\subsection{SAP 6.0 password checking functions}

\myindex{SAP}

One time when the author of this book have returned again to his SAP 6.0 IDES installed in a VMware box, he figured out that he forgot the password for the SAP* account, then he have recalled it, but then he got this error message 
\IT{<<Password logon no longer possible - too many failed attempts>>}, 
since he've made all these attempts in attempt to recall it.

\myindex{Windows!PDB}

The first extremely good news was that the full \IT{disp+work.pdb} \gls{PDB} file is supplied with SAP, and it contain almost everything: function names, structures, types, local variable and argument names, \etc{}. What a lavish gift!

There is TYPEINFODUMP\footnote{\url{http://go.yurichev.com/17038}} utility for converting \gls{PDB} files into something readable and grepable.

Here is an example of a function information + its arguments + its local variables:

\begin{lstlisting}
FUNCTION ThVmcSysEvent 
  Address:         10143190  Size:      675 bytes  Index:    60483  TypeIndex:    60484 
  Type: int NEAR_C ThVmcSysEvent (unsigned int, unsigned char, unsigned short*)
Flags: 0 
PARAMETER events 
  Address: Reg335+288  Size:        4 bytes  Index:    60488  TypeIndex:    60489 
  Type: unsigned int
Flags: d0 
PARAMETER opcode 
  Address: Reg335+296  Size:        1 bytes  Index:    60490  TypeIndex:    60491 
  Type: unsigned char
Flags: d0 
PARAMETER serverName 
  Address: Reg335+304  Size:        8 bytes  Index:    60492  TypeIndex:    60493 
  Type: unsigned short*
Flags: d0 
STATIC_LOCAL_VAR func 
  Address:         12274af0  Size:        8 bytes  Index:    60495  TypeIndex:    60496 
  Type: wchar_t*
Flags: 80 
LOCAL_VAR admhead 
  Address: Reg335+304  Size:        8 bytes  Index:    60498  TypeIndex:    60499 
  Type: unsigned char*
Flags: 90 
LOCAL_VAR record 
  Address: Reg335+64  Size:      204 bytes  Index:    60501  TypeIndex:    60502 
  Type: AD_RECORD
Flags: 90 
LOCAL_VAR adlen 
  Address: Reg335+296  Size:        4 bytes  Index:    60508  TypeIndex:    60509 
  Type: int
Flags: 90 
\end{lstlisting}

And here is an example of some structure:

\begin{lstlisting}
STRUCT DBSL_STMTID 
Size: 120  Variables: 4  Functions: 0  Base classes: 0
MEMBER moduletype 
  Type:  DBSL_MODULETYPE
  Offset:        0  Index:        3  TypeIndex:    38653
MEMBER module 
  Type:  wchar_t module[40]
  Offset:        4  Index:        3  TypeIndex:      831
MEMBER stmtnum 
  Type:  long
  Offset:       84  Index:        3  TypeIndex:      440
MEMBER timestamp 
  Type:  wchar_t timestamp[15]
  Offset:       88  Index:        3  TypeIndex:     6612
\end{lstlisting}

Wow!

Another good news: \IT{debugging} calls (there are plenty of them) are very useful. 

Here you can also notice the \IT{ct\_level} global variable\footnote{More about trace level: \url{http://go.yurichev.com/17039}}, that reflects the current trace level.

There are a lot of debugging inserts in the \IT{disp+work.exe} file:

\begin{lstlisting}[style=customasmx86]
cmp     cs:ct_level, 1
jl      short loc_1400375DA
call    DpLock
lea     rcx, aDpxxtool4_c ; "dpxxtool4.c"
mov     edx, 4Eh        ; line
call    CTrcSaveLocation
mov     r8, cs:func_48
mov     rcx, cs:hdl     ; hdl
lea     rdx, aSDpreadmemvalu ; "%s: DpReadMemValue (%d)"
mov     r9d, ebx
call    DpTrcErr
call    DpUnlock
\end{lstlisting}

If the current trace level is bigger or equal to threshold defined in the code here, 
a debugging message is to be written to the log files like \IT{dev\_w0}, \IT{dev\_disp}, 
and other \IT{dev*} files.

\myindex{\GrepUsage}

Let's try grepping in the file that we have got with the help of the TYPEINFODUMP utility:

\begin{lstlisting}
cat "disp+work.pdb.d" | grep FUNCTION | grep -i password
\end{lstlisting}

We have got:

\begin{lstlisting}
FUNCTION rcui::AgiPassword::DiagISelection 
FUNCTION ssf_password_encrypt 
FUNCTION ssf_password_decrypt 
FUNCTION password_logon_disabled 
FUNCTION dySignSkipUserPassword 
FUNCTION migrate_password_history 
FUNCTION password_is_initial 
FUNCTION rcui::AgiPassword::IsVisible 
FUNCTION password_distance_ok 
FUNCTION get_password_downwards_compatibility 
FUNCTION dySignUnSkipUserPassword 
FUNCTION rcui::AgiPassword::GetTypeName 
FUNCTION `rcui::AgiPassword::AgiPassword'::`1'::dtor$2 
FUNCTION `rcui::AgiPassword::AgiPassword'::`1'::dtor$0 
FUNCTION `rcui::AgiPassword::AgiPassword'::`1'::dtor$1 
FUNCTION usm_set_password 
FUNCTION rcui::AgiPassword::TraceTo 
FUNCTION days_since_last_password_change 
FUNCTION rsecgrp_generate_random_password 
FUNCTION rcui::AgiPassword::`scalar deleting destructor' 
FUNCTION password_attempt_limit_exceeded 
FUNCTION handle_incorrect_password 
FUNCTION `rcui::AgiPassword::`scalar deleting destructor''::`1'::dtor$1 
FUNCTION calculate_new_password_hash 
FUNCTION shift_password_to_history 
FUNCTION rcui::AgiPassword::GetType 
FUNCTION found_password_in_history 
FUNCTION `rcui::AgiPassword::`scalar deleting destructor''::`1'::dtor$0 
FUNCTION rcui::AgiObj::IsaPassword 
FUNCTION password_idle_check 
FUNCTION SlicHwPasswordForDay 
FUNCTION rcui::AgiPassword::IsaPassword 
FUNCTION rcui::AgiPassword::AgiPassword 
FUNCTION delete_user_password 
FUNCTION usm_set_user_password 
FUNCTION Password_API 
FUNCTION get_password_change_for_SSO 
FUNCTION password_in_USR40 
FUNCTION rsec_agrp_abap_generate_random_password 
\end{lstlisting}

Let's also try to search for debug messages which contain the words \IT{<<password>>} and \IT{<<locked>>}.
One of them is the string \IT{<<user was locked by subsequently failed password logon attempts>>} , referenced in \\
function \IT{password\_attempt\_limit\_exceeded()}.

Other strings that this function can write to a log file are: 
\IT{<<password logon attempt will be rejected immediately (preventing dictionary attacks)>>}, \IT{<<failed-logon lock: expired (but not removed due to 'read-only' operation)>>}, \IT{<<failed-logon lock: expired => removed>>}.

After playing for a little with this function, we noticed that the problem is exactly in it.
It is called from the \IT{chckpass()} function~---one of the password checking functions.

First, we would like to make sure that we are at the correct point:

Run \tracer:
\myindex{tracer}

\begin{lstlisting}
tracer64.exe -a:disp+work.exe bpf=disp+work.exe!chckpass,args:3,unicode
\end{lstlisting}

\begin{lstlisting}
PID=2236|TID=2248|(0) disp+work.exe!chckpass (0x202c770, L"Brewered1                               ", 0x41) (called from 0x1402f1060 (disp+work.exe!usrexist+0x3c0))
PID=2236|TID=2248|(0) disp+work.exe!chckpass -> 0x35
\end{lstlisting}

The call path is: \IT{syssigni()} -> \IT{DyISigni()} -> \IT{dychkusr()} -> \IT{usrexist()} -> \IT{chckpass()}.

The number 0x35 is an error returned in \IT{chckpass()} at that point:

\begin{lstlisting}[style=customasmx86]
.text:00000001402ED567 loc_1402ED567:                          ; CODE XREF: chckpass+B4
.text:00000001402ED567                 mov     rcx, rbx        ; usr02
.text:00000001402ED56A                 call    password_idle_check
.text:00000001402ED56F                 cmp     eax, 33h
.text:00000001402ED572                 jz      loc_1402EDB4E
.text:00000001402ED578                 cmp     eax, 36h
.text:00000001402ED57B                 jz      loc_1402EDB3D
.text:00000001402ED581                 xor     edx, edx        ; usr02_readonly
.text:00000001402ED583                 mov     rcx, rbx        ; usr02
.text:00000001402ED586                 call    password_attempt_limit_exceeded
.text:00000001402ED58B                 test    al, al
.text:00000001402ED58D                 jz      short loc_1402ED5A0
.text:00000001402ED58F                 mov     eax, 35h
.text:00000001402ED594                 add     rsp, 60h
.text:00000001402ED598                 pop     r14
.text:00000001402ED59A                 pop     r12
.text:00000001402ED59C                 pop     rdi
.text:00000001402ED59D                 pop     rsi
.text:00000001402ED59E                 pop     rbx
.text:00000001402ED59F                 retn
\end{lstlisting}

Fine, let's check:

\begin{lstlisting}
tracer64.exe -a:disp+work.exe bpf=disp+work.exe!password_attempt_limit_exceeded,args:4,unicode,rt:0
\end{lstlisting}

\begin{lstlisting}
PID=2744|TID=360|(0) disp+work.exe!password_attempt_limit_exceeded (0x202c770, 0, 0x257758, 0) (called from 0x1402ed58b (disp+work.exe!chckpass+0xeb))
PID=2744|TID=360|(0) disp+work.exe!password_attempt_limit_exceeded -> 1
PID=2744|TID=360|We modify return value (EAX/RAX) of this function to 0
PID=2744|TID=360|(0) disp+work.exe!password_attempt_limit_exceeded (0x202c770, 0, 0, 0) (called from 0x1402e9794 (disp+work.exe!chngpass+0xe4))
PID=2744|TID=360|(0) disp+work.exe!password_attempt_limit_exceeded -> 1
PID=2744|TID=360|We modify return value (EAX/RAX) of this function to 0
\end{lstlisting}

Excellent! We can successfully login now.

By the way, we can pretend we forgot the password, fixing the \IT{chckpass()} function to return a value of 0 is enough to bypass the check:

\begin{lstlisting}
tracer64.exe -a:disp+work.exe bpf=disp+work.exe!chckpass,args:3,unicode,rt:0
\end{lstlisting}

\begin{lstlisting}
PID=2744|TID=360|(0) disp+work.exe!chckpass (0x202c770, L"bogus                                   ", 0x41) (called from 0x1402f1060 (disp+work.exe!usrexist+0x3c0))
PID=2744|TID=360|(0) disp+work.exe!chckpass -> 0x35
PID=2744|TID=360|We modify return value (EAX/RAX) of this function to 0
\end{lstlisting}

What also can be said while analyzing the \\
\IT{password\_attempt\_limit\_exceeded()} 
function is that at the very beginning of it, this call can be seen:

\begin{lstlisting}[style=customasmx86]
lea     rcx, aLoginFailed_us ; "login/failed_user_auto_unlock"
call    sapgparam
test    rax, rax
jz      short loc_1402E19DE
movzx   eax, word ptr [rax]
cmp     ax, 'N'
jz      short loc_1402E19D4
cmp     ax, 'n'
jz      short loc_1402E19D4
cmp     ax, '0'
jnz     short loc_1402E19DE
\end{lstlisting}

Obviously, function \IT{sapgparam()} is used to query the value of some configuration parameter. This function can be called from 1768 different places.
It seems that with the help of this information, we can easily find the places in code, the control flow of which can be affected by specific configuration parameters.

It is really sweet. The function names are very clear, much clearer than in the \oracle. 
\myindex{\Cpp}

It seems that the \IT{disp+work} process is written in \Cpp. Has it been rewritten some time ago?

